Tweening SVGs
Like Plots.jl outputs! If you generate roughly the same image twice, then tweened_svg
will tween between the two images!
This was kind of quickly put together... feel free to contribute!
👀 Reading hidden code
Example
👀 Reading hidden code
👀 Reading hidden code
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
👀 Reading hidden code
👀 Reading hidden code
Deprecated, use `AbstractPlutoDingetjes.Display.published_to_js(x)` instead of `PlutoRunner.publish_to_js(x)`.
GIF recording
If you're viewing this online, here is a video:
👀 Reading hidden code
More example!
👀 Reading hidden code
👀 Reading hidden code
0.579353
0.212619
0.249464
0.954094
0.338887
0.00133008
0.0428746
0.369778
0.0630689
0.0706686
0.721544
0.662017
0.432925
0.608376
0.402925
0.726787
0.169937
0.820568
0.726907
0.286489
👀 Reading hidden code
0.825183
0.288821
0.407403
0.690683
0.817704
0.535497
0.00786093
0.583069
0.093637
0.27819
0.297853
0.791991
0.280142
0.794022
0.840612
0.870215
0.515576
0.733609
0.569025
0.296797
0.174456
0.969056
0.447828
0.149678
0.97611
0.801106
0.395016
0.655791
0.96969
0.351328
👀 Reading hidden code
@bind num Slider(1:50)
👀 Reading hidden code
left = let
regenerate
p = plot(title="Hello from Pluto.jl!"; ylim=(0,1), xlim=(0,30))
plot!(p, baseline .* .5;
fillrange=zeros(20), fillopacity=.2, fillcolor="gray",
color="gray", linestyle=:dash,
label="Baseline")
plot!(p, num:num+10, big_data[num:num+10];
label="Mitigation")
p
end |> tweened_svg
👀 Reading hidden code
Deprecated, use `AbstractPlutoDingetjes.Display.published_to_js(x)` instead of `PlutoRunner.publish_to_js(x)`.
# left = let
# regenerate
# demo_plot(rand(20); ylim=(0,1))
# end
👀 Reading hidden code
Two SVG files
👀 Reading hidden code
@bind before FilePicker()
👀 Reading hidden code
@bind after FilePicker()
👀 Reading hidden code
MethodError: no method matching getindex(::Nothing, ::String)
Here is what happened, the most recent locations are first:
(
before=Show(MIME"image/svg+xml"(), before["data"]),
after=Show(MIME"image/svg+xml"(), after["data"])
)
👀 Reading hidden code
👀 Reading hidden code
Show second image:
👀 Reading hidden code
TypeError: non-boolean (Missing) used in boolean context
Here is what happened, the most recent locations are first:
Maybe time for a break? ☕️
tweened_svg((which ? after : before)["data"])
👀 Reading hidden code
Appendix
👀 Reading hidden code
begin
using Plots
Plots.default(linewidth=5, size=(600,200))
end
👀 Reading hidden code
using HypertextLiteral
👀 Reading hidden code
using PlutoUI
👀 Reading hidden code
better_publish_to_js (generic function with 1 method)
👀 Reading hidden code
tweened_svg (generic function with 1 method)
function tweened_svg(input)
svg_data =
(input isa Vector{UInt8}) ? String(copy(input)) :
(input isa String) ? input :
repr(MIME"image/svg+xml"(), input)
@htl """
<script id="hellooozz">
const parser = new DOMParser()
const after = $(better_publish_to_js(svg_data))
const after_svg = parser.parseFromString(
after,
"image/svg+xml"
).firstElementChild
const first_time = this == null
if(first_time) {
return after_svg
}
const before_svg = this
const b_paths = Array.from(before_svg.querySelectorAll("path,polyline"))
const a_paths = Array.from(after_svg.querySelectorAll("path,polyline"))
const same_structure = a_paths.length === b_paths.length &&
a_paths.every((x,i) => x.tagName === b_paths[i].tagName)
if(!same_structure) {
console.info("Not tweening SVGs because the structures are too different.", a_paths, b_paths)
return after_svg
} else {
b_paths.forEach((path,i) => {
// if(path.tagName === "path") {
// const old_path = b_paths[i].getAttribute("d")
// const new_path = a_paths[i].getAttribute("d")
// if(old_path !== new_path) {
// console.log(i)
// const plainShapeObject = {
// el: path,
// d: old_path,
// }
// const plainShapeObjectTo = {
// el: path,
// d: new_path,
// }
// const path_clone = Wilderness.shape(
// plainShapeObject,
// plainShapeObjectTo,
// {replace: path}
// )
// // const animation = Wilderness.timeline(path_clone, {
// // iterations: Infinity,duration: 2000,
// // alternate: true
// // })
// // Wilderness.render(after_svg, animation)
// // Wilderness.play(animation)
// console.log("Playing!")
// }
// } else if(path.tagName === "polyline") {
const attr_name =
path.tagName === "polyline" ? "points" :
path.tagName === "path" ? "d" :
undefined
const old_path = b_paths[i].__pluto_current_path ?? b_paths[i].getAttribute(attr_name)
// // console.log(old_path)
const new_path = a_paths[i].getAttribute(attr_name)
if(old_path !== new_path) {
console.log("Animating!")
// const el = svg`<\${path.tagName}>
// <animate
// attributeName="\${attr_name}"
// dur="2s"
// values="\${old_path + "; " + new_path}"
// repeatCount="indefinite"
// ></animate>
// </\${path.tagName}>`
const animate = path.querySelector("animate") ?? document.createElementNS("http://www.w3.org/2000/svg", "animate")
animate.setAttribute("attributeName", attr_name)
animate.setAttribute("dur", `200ms`)
animate.setAttribute("values", `\${old_path}; \${new_path}`)
animate.setAttribute("repeatCount", `1`)
animate.setAttribute("fill", "freeze")
path.appendChild(animate)
// path.removeAttribute(attr_name)
path.__pluto_current_path = new_path
animate.beginElement()
// path.getAttributeNames().forEach(name => {
// if(name !== attr_name) {
// el.setAttribute(name, path.getAttribute(name))
// }
// })
// path.replaceWith(el)
}
// if(old_path !== new_path) {
// console.log(i)
// const plainShapeObject = {
// el: path,
// points: old_path,
// }
// const plainShapeObjectTo = {
// el: path,
// points: new_path,
// }
// const path_clone = Wilderness.shape(
// plainShapeObject,
// plainShapeObjectTo,
// {replace: path}
// )
// const animation = Wilderness.timeline(path_clone, {
// iterations: Infinity,duration: 2000,
// alternate: true
// })
// Wilderness.render(after_svg, animation)
// Wilderness.play(animation)
// console.log("Playing!")
// }
// }
})
}
return before_svg
</script>
"""
end
👀 Reading hidden code